/**
 * 
 */
package org.youisoft.summer.evaluator.eval;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.youisoft.summer.evaluator.EvalConst;
import org.youisoft.summer.evaluator.EvaluateException;
import org.youisoft.summer.evaluator.InvokeException;
import org.youisoft.summer.evaluator.OpEnum;
import org.youisoft.summer.evaluator.PlaceHolder;
import org.youisoft.summer.evaluator.UnitEnum;
import org.youisoft.summer.evaluator.context.CalContext;
import org.youisoft.summer.evaluator.eval.ExpressionEvaluator;
import org.youisoft.summer.evaluator.operator.Operator;
import org.youisoft.summer.evaluator.registry.DefaultFunctionRegistry;
import org.youisoft.summer.evaluator.unit.Bracket;
import org.youisoft.summer.evaluator.unit.Parenthesis;
import org.youisoft.summer.evaluator.unit.Brace;
import org.youisoft.summer.evaluator.unit.UnitVisitor;
import org.youisoft.summer.evaluator.unit.VisitorUnit;
import org.youisoft.summer.evaluator.unit.VisitorUnit.Iterator;
import org.youisoft.summer.evaluator.util.Ulitity;

/**
 * 
 * @author zijiang.jl
 *
 * @param <R>
 * @param <P>
 */
public abstract class AbstractExpressionEvaluator<C,R,P> implements ExpressionEvaluator<C,R,P> {
	protected static final Log logger=LogFactory.getLog(ExpressionEvaluator.class);
	
	private String parenthesisClassName;
	
	private String braceClassName;
	
	private String bracketClassName;
	
	private java.lang.ThreadLocal<VisitorUnit<R,P>> visitorUnit=new java.lang.ThreadLocal<VisitorUnit<R,P>>();
	
	private java.lang.ThreadLocal<UnitVisitor<R, P>> visitor=new java.lang.ThreadLocal<UnitVisitor<R, P>>();
	
	private ExpressionTransformer expressionTransformer;
	
	private DefaultFunctionRegistry registry=new DefaultFunctionRegistry();
	
	private AtomicBoolean initilize=new AtomicBoolean(false);
	
	private long clearCachePeriod=300;
	
	private java.lang.ref.ReferenceQueue<VisitorUnit<R,P>> rq=new java.lang.ref.ReferenceQueue<VisitorUnit<R,P>>();
	
	private java.util.concurrent.ScheduledExecutorService executor=java.util.concurrent.Executors.newScheduledThreadPool(1);
	
	private final java.util.Map<String, VisitorUnit<R,P>> unitCache=new java.util.HashMap<String, VisitorUnit<R,P>>();
	
	
	private final java.util.Map<String,UnitReference> unitRef=new java.util.concurrent.ConcurrentHashMap<String,UnitReference>();
	
	public String getCurrentExpression(){
		return this.visitorUnit.get().getSource()==null?null:this.visitorUnit.get().getSource().toString();
	}
	
	@Override
	public void init() {
		logger.debug("init......");
		if(this.parenthesisClassName==null) throw new java.lang.RuntimeException("Please set parenthesis class");
		if(this.braceClassName==null) throw new java.lang.RuntimeException("Please set brace class");
		if(this.bracketClassName==null) throw new java.lang.RuntimeException("Please set bracket class");
		if(this.expressionTransformer==null) this.expressionTransformer=new ExpressionTransformer.DefaultExpressionTransformer();
		
		executor.scheduleAtFixedRate(new java.lang.Runnable(){
			@Override
			public void run() {
				@SuppressWarnings("unchecked")
				UnitReference tr=(UnitReference)rq.poll();
				while(tr!=null){
					unitRef.remove(tr.getKey());
				}
			}
			
		},5,clearCachePeriod,TimeUnit.SECONDS);
		
		this.initilize.set(true);
	}
	
	
	@Override
	public ExpressionEvaluator<C,R,P> prepare(String expression) throws EvaluateException{
		if(this.visitor.get()==null) this.visitor.set(new UnitVisitor<R,P>());
		this.visitorUnit.set(this.split(this.expressionTransformer.transform(expression)));
		return this;
	}
	

	/**
	 * @author zijiang.jl
	 *
	 */
	private class UnitReference extends java.lang.ref.SoftReference<VisitorUnit<R,P>>{
		private String key;
		public UnitReference(String key,VisitorUnit<R,P> unit){
			super(unit,rq);
			this.key=key;
		}
		public final String getKey() {
			return key;
		}
		
		
	}

	protected VisitorUnit<R,P> split(String str) throws EvaluateException{
//		if(str==null) throw new EvaluateException("expression is null");
//		if(unitCache.get(str)==null){
//			VisitorUnit<R,P> unit=this.splitUnit(str);
//			unitCache.put(str, unit);
//			return unit;
//		}else{
//			return unitCache.get(str);
//		}
		
		
		VisitorUnit<R,P> unit=null;
		UnitReference ref=unitRef.get(str);
		if(ref==null){
			unit=this.splitUnit(str);
			unitRef.put(str,new UnitReference(str,unit));
		}else{
			unit=(Parenthesis<R,P>)ref.get();
			if(unit==null){
				unit=this.splitUnit(str);
			}
		}
		return unit;
	}

	
	private VisitorUnit<R,P> splitUnit(String str) throws EvaluateException{
		java.util.Stack<VisitorUnit<R,P>> unitSt=new java.util.Stack<VisitorUnit<R,P>>();
		java.util.Stack<Integer> braceSt=new java.util.Stack<Integer>();
		VisitorUnit<R,P> parentUnit=null;
		VisitorUnit<R,P> unit=null;
		char[] strChar=str.toCharArray();
		int opIndex=-1;
		for(int i=0;i<strChar.length;i++){
			if(braceSt.isEmpty()){
				if(EvalConst.COMA==strChar[i]||OpEnum.isOpChar(strChar[i])) opIndex=i;
				if(EvalConst.LEFTPARENTHESIS==strChar[i]){
					unit=this.getNewParenthesisObject();
					unit.setSource(str);
					unit.setLeftIndex(i);
					unit.setRegistry(this.getRegistry());
					if(!unitSt.isEmpty()) {
						parentUnit=unitSt.peek();
						unit.setParentUnit(parentUnit);
						parentUnit.addChildUnit(unit);
					}
					
					if(opIndex>=0&&i-opIndex>1) {
						unit.setFunIndex(opIndex+1);
						unit.setfName(str.substring(opIndex+1,i));
					}
					
					if(opIndex<0
						&&unit.getParentUnit()!=null
						&&unit.getParentUnit().getLeftIndex()+1!=unit.getLeftIndex()){
						unit.setFunIndex(unit.getParentUnit().getLeftIndex()+1);
						unit.setfName(str.substring(unit.getParentUnit().getLeftIndex()+1,i));
					}
					unitSt.push(unit);
					opIndex=-1;
				}else if(EvalConst.RIGHTPARENTHESIS==strChar[i]){
					unit=unitSt.pop();
					unit.setRightIndex(i);
					
					//========================
					java.lang.StringBuilder sb=new java.lang.StringBuilder("");
					int a=unit.getLeftIndex()+1;
					int b;
					Iterator<VisitorUnit<R, P>> it=unit.iterator();
					UnitEnum ue=null;
					VisitorUnit<R, P> vu;
					while((ue=it.hasNext())!=null){
						vu=it.next();
						if(Ulitity.hasFName(vu)){
							b=vu.getFunIndex();
						}else{
							b=vu.getLeftIndex();
						}
						switch(ue){
							case parenthesis:
								sb.append(str.substring(a,b));
								sb.append(PlaceHolder.instance.getLeftParenthesisPlaceHolder());
								sb.append(it.currentPosition());
								sb.append(PlaceHolder.instance.getRightParenthesisPlaceHolder());
							break;
							case brace:
								sb.append(str.substring(a,b));
								sb.append(PlaceHolder.instance.getLeftBracePlaceHolder());
								sb.append(it.currentPosition());
								sb.append(PlaceHolder.instance.getRightBracePlaceHolder());
								break;
							case bracket:
								sb.append(str.substring(a,b));
								sb.append(PlaceHolder.instance.getLeftBracketPlaceHolder());
								sb.append(it.currentPosition());
								sb.append(PlaceHolder.instance.getRightBracketPlaceHolder());
								break;
							default:
						}
						a=vu.getRightIndex()+1;
					}
					sb.append(str.substring(a,unit.getRightIndex()));
					unit.setContent(sb.toString());
					unit.init();
				}else if(EvalConst.LEFTBRACKET==strChar[i]){
					unit=this.getNewBracketObject();
					unit.setSource(str);
					unit.setLeftIndex(i);
					unit.setRegistry(this.getRegistry());
					if(!unitSt.isEmpty()) {
						parentUnit=unitSt.peek();
						unit.setParentUnit(parentUnit);
						parentUnit.addChildUnit(unit);
					}
					
					if(opIndex>=0&&i-opIndex>1) {
						unit.setFunIndex(opIndex+1);
						unit.setfName(str.substring(opIndex+1,i));
					}
					
					if(opIndex<0
						&&unit.getParentUnit()!=null
						&&unit.getParentUnit().getLeftIndex()+1!=unit.getLeftIndex()){
						unit.setFunIndex(unit.getParentUnit().getLeftIndex()+1);
						unit.setfName(str.substring(unit.getParentUnit().getLeftIndex()+1,i));
					}
					unitSt.push(unit);
					opIndex=-1;
				}else if(EvalConst.RIGHTBRACKET==strChar[i]){
					unit=unitSt.pop();
					unit.setRightIndex(i);
					
					//========================
					java.lang.StringBuilder sb=new java.lang.StringBuilder("");
					int a=unit.getLeftIndex()+1;
					int b;
					Iterator<VisitorUnit<R, P>> it=unit.iterator();
					UnitEnum ue=null;
					VisitorUnit<R, P> vu;
					while((ue=it.hasNext())!=null){
						vu=it.next();
						if(Ulitity.hasFName(vu)){
							b=vu.getFunIndex();
						}else{
							b=vu.getLeftIndex();
						}
						switch(ue){
							case parenthesis:
								sb.append(str.substring(a,b));
								sb.append(PlaceHolder.instance.getLeftParenthesisPlaceHolder());
								sb.append(it.currentPosition());
								sb.append(PlaceHolder.instance.getRightParenthesisPlaceHolder());
							break;
							case brace:
								sb.append(str.substring(a,b));
								sb.append(PlaceHolder.instance.getLeftBracePlaceHolder());
								sb.append(it.currentPosition());
								sb.append(PlaceHolder.instance.getRightBracePlaceHolder());
								break;
							case bracket:
								sb.append(str.substring(a,b));
								sb.append(PlaceHolder.instance.getLeftBracketPlaceHolder());
								sb.append(it.currentPosition());
								sb.append(PlaceHolder.instance.getRightBracketPlaceHolder());
								break;
							default:
						}
						a=vu.getRightIndex()+1;
					}
					sb.append(str.substring(a,unit.getRightIndex()));
					unit.setContent(sb.toString());
					unit.init();
				}
			}
			
			if(EvalConst.LEFTBRACE==strChar[i]){
				braceSt.push(i);
			}else if(EvalConst.RIGHTBRACE==strChar[i]){
				int index=braceSt.pop();
				if(braceSt.isEmpty()){
					unit=this.getNewBraceObject();
					unit.setSource(str);
					unit.setLeftIndex(index);
					unit.setRightIndex(i);
					unit.setContent(str.substring(index+1,i));
					unit.setRegistry(this.getRegistry());
					if(opIndex>=0&&index-opIndex>1) {
						unit.setFunIndex(opIndex+1);
						unit.setfName(str.substring(opIndex+1,index));
					}
				
					if(!unitSt.isEmpty()){
						VisitorUnit<R,P> par=unitSt.peek();
						par.addChildUnit(unit);
						unit.setParentUnit(par);
						if(opIndex<0
								&&unit.getParentUnit()!=null
								&&unit.getParentUnit().getLeftIndex()+1!=unit.getLeftIndex()){
								unit.setFunIndex(unit.getParentUnit().getLeftIndex()+1);
								unit.setfName(str.substring(unit.getParentUnit().getLeftIndex()+1,index));
							}
					}
					unit.init();
					opIndex=-1;
				}
				
			}
		}
		if(!unitSt.isEmpty()) throw new java.lang.RuntimeException("The expression is error,pls check the parenthesis!");
		return unit;
	}
	
	
	@Override
	public void reInit() {
		this.clear();
		this.init();
	}
	
	

	@Override
	public boolean isInit() {
		
		return this.initilize.get();
	}
	
	

	@Override
	public void clear() {
		this.expressionTransformer.clear();
		this.unitCache.clear();
		this.unitRef.clear();
	}

	@Override
	public void destory() {
		this.visitor=null;
		this.expressionTransformer=null;
		this.parenthesisClassName=null;
		initilize.set(false);
		
	}

	@SuppressWarnings("unchecked")
	private Parenthesis<R,P> getNewParenthesisObject() throws EvaluateException{
		try {
			return (Parenthesis<R,P>)this.getClass().getClassLoader().loadClass(this.getParenthesisClassName()).newInstance();
		} catch (Exception e) {
			throw new EvaluateException("Can't create parenthesis object", e);
		}
	}

	@SuppressWarnings("unchecked")
	private Brace<R,P> getNewBraceObject() throws EvaluateException{
		try {
			return (Brace<R,P>)this.getClass().getClassLoader().loadClass(this.getBraceClassName()).newInstance();
		} catch (Exception e) {
			throw new EvaluateException("Can't create Brace object", e);
		}
	}
	
	@SuppressWarnings("unchecked")
	private Bracket<R,P> getNewBracketObject() throws EvaluateException{
		try {
			return (Bracket<R,P>)this.getClass().getClassLoader().loadClass(this.getBracketClassName()).newInstance();
		} catch (Exception e) {
			throw new EvaluateException("Can't create Bracket object", e);
		}
	}
	
	@Override
	public boolean evaluateBoolean(CalContext<C, R, P> context,String expression) {
		try {
			Object obj=this.evaluate(context,expression);
			if(obj!=null&&("true".equalsIgnoreCase(obj.toString().trim())||"1.0".equals(obj.toString().trim())||"1".equals(obj.toString().trim()))) return true;
			return false;
		} catch (Exception e) {
			throw new java.lang.RuntimeException(e.getMessage(),e);
		}
	}
	
	

	@Override
	public Double evaluateDouble(CalContext<C, R, P> context,String expression) {
		try {
			Object obj=this.evaluate(context,expression);
			return Double.parseDouble(obj.toString());
		} catch (Exception e) {
			throw new java.lang.RuntimeException(e.getMessage(),e);
		}
	}

	
	@Override
	public Object evaluate(CalContext<C,R,P> context,String expression) throws EvaluateException,InvokeException{
		return this.prepare(expression).evaluate(context);
	}
	
	@Override
	public Object evaluate(CalContext<C,R,P> context) throws EvaluateException,InvokeException{
		return this.evaluateOperator(context).value();
	}
	
	
	
	

	public Operator<?> evaluateOperator(CalContext<C, R, P> context)
			throws EvaluateException,InvokeException {
		if(!this.isInit()) throw new EvaluateException("please initilize ExpressionEvaluator!");
		if(this.visitorUnit.get()==null) throw new EvaluateException("please prepare expression!");
		context.setEvaluatorClassName(this.getClass().getName());
		context.setParenthesisClassName(this.getParenthesisClassName());
		context.setBraceClassName(this.getBraceClassName());
		context.setBracketClassName(this.getBracketClassName());
		this.visitor.get().setProperty(context.getProperty());
		this.visitor.get().setResource(context.getResource());

		this.visitorUnit.get().accept(context,this.visitor.get());
		if(context.getThrowable()!=null){
			if(EvaluateException.class.isAssignableFrom(context.getThrowable().getClass())){
				EvaluateException e= EvaluateException.class.cast(context.getThrowable());
				context.setThrowable(null);
				throw e;
			}else if(InvokeException.class.isAssignableFrom(context.getThrowable().getClass())){
				InvokeException i=InvokeException.class.cast(context.getThrowable());
				context.setThrowable(null);
				throw i;
			}else{
				EvaluateException ee=new EvaluateException(this.getCurrentExpression(),context.getThrowable());
				context.setThrowable(null);
				throw ee;
			}
		}
		
		return this.visitor.get().getValue();
	}

	
	
	public final String getBracketClassName() {
		return bracketClassName;
	}

	@Override
	public final void setBracketClassName(String bracketClassName) {
		this.bracketClassName = bracketClassName;
	}

	public final String getParenthesisClassName() {
		return parenthesisClassName;
	}
	
	@Override
	public final void setParenthesisClassName(String parenthesisClassName) {
		this.parenthesisClassName = parenthesisClassName;
	}

	public final String getBraceClassName() {
		return braceClassName;
	}
	
	@Override
	public final void setBraceClassName(String braceClassName) {
		this.braceClassName = braceClassName;
	}

	@Override
	public UnitVisitor<R, P> getVisitor() {
		
		return visitor.get();
	}

	@Override
	public void setVisitor(UnitVisitor<R, P> visitor) {
		this.visitor.set(visitor);
	}
	

	@Override
	public final ExpressionTransformer getExpressionTransformer() {
		return expressionTransformer;
	}

	@Override
	public final void setExpressionTransformer(ExpressionTransformer expressionTransformer) {
		this.expressionTransformer = expressionTransformer;
	}



	public final long getClearCachePeriod() {
		return clearCachePeriod;
	}

	public final void setClearCachePeriod(long cachePeriod) {
		clearCachePeriod = cachePeriod;
	}

	
	
	@Override
	public void registerAlias(String name, String... alias) {
		registry.registerAlias(name, alias);
		
	}

	@Override
	public void register(Object object) {
		registry.register(object);
		
	}

	@Override
	public void unRegister(String name) {
		registry.unRegister(name);
		
	}

	@Override
	public void register(String name, Object object) {
		registry.register(name, object);
		
	}

	public final DefaultFunctionRegistry getRegistry() {
		return registry;
	}

	public final void setRegistry(DefaultFunctionRegistry registry) {
		this.registry = registry;
	}

	

	
	
}
